/* 4.Watcher 升级后 */
/* 0.2 不用通过 vue实例.data.属性更改 属性直接通过vue实例.属性 */
/* 原理是在 实例vue的时候 再给 data 包一层 get set */

/* 
* 1. 当前版本 还不具有模板功能，单纯只是把 绑定的dom节点的 innerHTML 替换为了 data里面的属性
* 2. 也就是 {{}} 是无意义的 里面的 属性也是无意义的。 
* 3. 请看 v0.3
*/

function propsHijack(data,key,val){
  observe(val); //深度劫持 对象所有属性
  var dep = new Dep()
  Object.defineProperty(data,key,{
    enumerable:true, // 允许枚举
    configurable:true, // 允许配置属性
    //1. 只要后面订阅者 [[get]] 了data, 当前对应属性的 Watcher实例就会添加到Dep队列中
    get() {
      if (Dep.target) { // Watcher存在的话 添加订阅者
        dep.addSub(Dep.target); // 这里添加一个订阅者
      }
      return val
    }, 
    //2. 一旦有任何一个属性变动了，就触发整体更新 
    set(newVal) {
      if (val === newVal) return 
      val = newVal
      console.log(`属性 ${key} 已经被监听了，现在他的值为 ${newVal.toString()}`)
      dep.notify() // 如果数据变化，通知*所有*订阅者
    }
  })
}

/* 监听器 */     
function observe(data){
  // 没有属性了，或属性不是对象了时 便退出遍历。
  if (!data || (typeof data !== 'object')) { 
    return
  }
  Object.keys(data).forEach( key => {
    propsHijack(data,key,data[key])
  })
}


// 订阅者管理器 dep (发布者)
class Dep {
  subs = [] // Watcher 订阅者 队列
  static target = null // 缓存当前的 watcher
  addSub(sub) {
    this.subs.push(sub)
  }
  notify(){
    this.subs.forEach(sub => {
      sub.update() // 通知每个订阅者检查更新
    })
  }
}

// 开始实现 订阅者 Watcher
class Watcher {
  constructor (vm, exp, cb) {
    this.vm = vm // 指向bzjVue的作用域(bzjVue实例)
    this.exp = exp // 绑定属性的key
    this.cb = cb // 闭包
    this.value = this.get() // 实例Watcher 就会触发get() ，该this.value用于存放 更新前（初始化）的数据
  }
  get () {
    Dep.target = this // 缓存自己
   /* 
   *  强制执行监听器（propsHijack函数里 属性的 Object.defineProperty 中的）的get函数 
   *  就是让 该watcher 获取 被监视的属性的值  
   */
  /* 注意： 这一步，中的 this.vm.data[this.exp] 就会立马触发 该属性的 [[get]]， 会把当前 Watcher实例 插入Dep 的 Watcher队列中*/
    const value = this.vm.data[this.exp] 
    Dep.target = null // value 拿到值后，就释放自己。方便下一个 watcher实例缓存
    return value
  }
  run() {
    const value = this.vm.data[this.exp]
    const oldVal = this.value
    if (value !== oldVal) {
      this.value = value
      this.cb.call(this.vm,value,oldVal)
    }
  }
  update() {
    this.run()
  }
}
// 链接 Observer 和 Watcher 。 响应式初步完成
class Vue {
  constructor (data, el, exp) {
    this.data = data
    // 遍历data 劫持data所有的属性
    observe(data)
    // 再给data包一层 get set 使得实例可以直接访问到 具体属性，而不用通过 data
    // 只用浅绑定 一层 就对了 不用写data 目的就达到了 => this.form.xxx
    // 其实也是个诡计 就是把data里的属性 给拷贝了份 放到 实例本身上了而已，并且使其不可被枚举
    // 修改 这些属性 data内部的属性已经会跟着改变的（响应）。（注意 get set）
    Object.keys(data).forEach( key=> {
      Object.defineProperty(this, key, {
        enumerable:false,
        configurable:true,
        get() {
          return this.data[key]
        },
        set(newVal) {
          return this.data[key] = newVal
        }
      })
    } )
    // 数据初始化
    el.innerHTML = this.data[exp]
    // 监听数据改变， 实现响应
    new Watcher(this, exp, function(value) {
      el.innerHTML = value 
    })
  }
}
